#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2020/12/22 15:34
# @Author  : CHEN Wang
# @Site    : 
# @File    : brinson_attribution.py
# @Software: PyCharm 

"""
脚本说明: 基金Brinson归因
"""

import numpy as np
import pandas as pd
# from quant_researcher.quant.datasource_fetch.fund_api.fund_holding_related import \
#     get_holding_report_info
from quant_researcher.quant.datasource_fetch.fund_api.fund_holding_related import \
    get_holding_report_info
from quant_researcher.quant.performance_attribution.core_functions.attribution_analysis.brinson.brinson_attribution import \
    get_stk_hold_indu_brinson_attribution
from quant_researcher.quant.datasource_fetch.index_api.index_info import get_sw_industry_name
from quant_researcher.quant.performance_attribution.core_functions.attribution_analysis import \
    multi_period_attribution


def fund_indus_brinson_attribution(fund_code='110023', benchmark='000300',
                                   start_date='2019-12-31', end_date='2020-06-30', **kwargs):
    conn_base = kwargs.pop('conn_base', None)
    conn_ty = kwargs.pop('conn_ty', None)
    conn_stock = kwargs.pop('conn_stock', None)
    # 获取期初期末股票持仓
    stock_p_begin = get_holding_report_info(report='stock', fund_code=[fund_code], report_type=[5, 6],
                                            end_date=start_date, select=['stock_code', 'pptinnv'], conn=conn_base)
    stock_p_end = get_holding_report_info(report='stock', fund_code=[fund_code], report_type=[5, 6],
                                          end_date=end_date, select=['stock_code', 'pptinnv'], conn=conn_base)
    stock_p_begin.columns = ['security_code', 'security_weight']
    stock_p_end.columns = ['security_code', 'security_weight']
    stock_p_begin['security_weight'] /= 100
    stock_p_end['security_weight'] /= 100

    # 根据期初期末股票持仓进行行业Brinson归因
    total_attribution, indu_attribution_df = get_stk_hold_indu_brinson_attribution(
        fund_code=fund_code,
        start_date=start_date, end_date=end_date,
        holding_begin=stock_p_begin,
        holding_end=stock_p_end,
        benchmark=benchmark,
        conn_base=conn_base, conn_stock=conn_stock, conn_ty=conn_ty)

    return total_attribution, indu_attribution_df


def fund_indu_brinson_attribution_multi_period(fund_code='110022', start_date='2019-06-30',
                                               end_date='2020-06-30', benchmark='000300', **kwargs):
    def trans_freq(df):
        df.index = pd.to_datetime(df.index)
        nav_q = df.asfreq('Q', method='ffill')
        nav_semi_a = nav_q[nav_q.index.month.isin([6, 12])]
        return nav_semi_a

    conn_base = kwargs.pop('conn_base', None)
    conn_ty = kwargs.pop('conn_ty', None)
    conn_stock = kwargs.pop('conn_stock', None)

    date_range = pd.date_range(start_date, end_date, freq='Q')
    date_to_calc = date_range[date_range.month.isin([6, 12])].strftime('%Y-%m-%d').tolist()
    attribution_list = []
    indu_list = []
    for start, end in zip(date_to_calc[:-1], date_to_calc[1:]):
        attribution, industry = fund_indus_brinson_attribution(fund_code=fund_code,
                                                               benchmark=benchmark,
                                                               start_date=start, end_date=end,
                                                               conn_base=conn_base, conn_stock=conn_stock, conn_ty=conn_ty)
        attribution = attribution.rename(end)
        attribution_list.append(attribution)
        indu_list.append(industry)
    attribution_df = pd.concat(attribution_list, axis=1).T
    industry_df = pd.concat(indu_list)

    period_attribution_list = []
    period_indu_attribution_list = []
    for date in attribution_df.index:
        # 进行进行多期Brinson收益统计
        attribution_df_temp = attribution_df.loc[:date,
                              ['all_allocation_ability', 'all_manage_ability',
                               'all_cross_ability', 'stock_holding_ret', 'weighted_index_ret',
                               'stock_active_ret']]

        fund_ret_series = attribution_df.loc[:date, 'fund_ret']
        multi_period_coefficient = multi_period_attribution.absolute_multi_period(
            fund_ret_series)  # 获取多期归因系数

        weighted_ret = attribution_df_temp.mul(multi_period_coefficient, axis=0)
        cummulated_ret = weighted_ret.sum(axis=0)
        cummulated_ret['end_date'] = date
        period_attribution_list.append(cummulated_ret)

        # 分行业进行多期Brinson收益统计
        industry_df_temp = industry_df[industry_df['end_date'] <= date]
        industry_df_temp = industry_df_temp.reset_index().set_index(['end_date'])
        industry_df_temp = industry_df_temp.merge(pd.DataFrame(multi_period_coefficient),
                                                  right_index=True,
                                                  left_index=True, how='left')
        industry_df_temp.iloc[:, 1:-1] = industry_df_temp.iloc[:, 1:-1].mul(
            industry_df_temp.iloc[:, -1], axis=0)
        cummulate_industry_ret = industry_df_temp.groupby('indu_name').sum()
        cummulate_industry_ret.drop(columns=['multi_period_coff'], inplace=True)
        cummulate_industry_ret['end_date'] = date
        period_indu_attribution_list.append(cummulate_industry_ret)

    period_attribution = pd.DataFrame(period_attribution_list).set_index(['end_date'])
    period_attribution.loc[start_date, :] = 0  # 把初始收益率0加进去
    period_attribution.sort_index(inplace=True)

    period_indu_attribution = pd.concat(period_indu_attribution_list, axis=0)
    period_indu_attribution = period_indu_attribution.reset_index().rename(
        columns={'indu_name': 'industry_name'})
    sw_name = get_sw_industry_name()
    period_indu_attribution = period_indu_attribution.merge(sw_name, on='industry_name',
                                                            how='left').set_index(
        'industry_code')

    # 获取从起始日至今的累计收益率归因
    latest_attribution = period_attribution.iloc[-1]

    # 获取从起始日至今的各行业累计收益率归因
    latest_indu_attribution = period_indu_attribution[
        period_indu_attribution['end_date'] == end_date]

    return latest_indu_attribution, latest_attribution, period_attribution


if __name__ == '__main__':
    fund_code = '110022'
    benchmark = '000300'

    # 测试单期Brinson
    start_date = '2019-12-31'
    end_date = '2020-06-30'
    test = fund_indus_brinson_attribution(fund_code=fund_code, benchmark=benchmark,
                                          start_date=start_date, end_date=end_date)

    # 测试多期Brinson
    start_date = '2019-06-30'
    end_date = '2020-06-30'
    test = fund_indu_brinson_attribution_multi_period(fund_code=fund_code, benchmark=benchmark,
                                                      start_date=start_date, end_date=end_date)
